import http from 'http';
import Base64 from 'crypto-js/enc-base64';
import MD5 from 'crypto-js/md5';
import HmacSHA1 from 'crypto-js/hmac-sha1';
import { v4 as uuidV4 } from 'uuid';
import dayjs from 'dayjs';

import config from '../../config';
import HttpHelper from '../../utils/HttpHelper';
import * as Enums from '../../utils/Enums';

/**
 * 根据文档，每个服务的 auth headers key 大小写都有些许差异，
 * 对 requestDate 和 hmac 的要求也各有细微区别，需要加以区分
 */
const buildServiceRequestAuthHeaders = (request: http.IncomingMessage) => {
  const requestPath = getRequestPath(request);
  const { console, eshield, firewall, cloudAudit, logsAudit } = config.business;
  switch (request.headers.backend) {
    case Enums.ServiceTypes.ProxyAppOrder:
      return buildOrderAuthHeader(request);
    case Enums.ServiceTypes.Console:
      return {
        ...buildCommonAuthHeader(console.ak, console.sk, requestPath),
        consoleUrl: config.business.console.aimUrl,
      };
    case Enums.ServiceTypes.EShield:
      return buildCommonAuthHeader(eshield.ak, eshield.sk, requestPath?.replace('/ctcloud/center', ''));
    // 服务器安全卫士访问订单相关 API 时，request header 需要设置为 eshield-bsscrm 而不能是 it，否则会 403
    case Enums.ServiceTypes.IT:
    case Enums.ServiceTypes.EShieldBssCrm:
      return buildEShieldBssCrmAuthHeader();
    case Enums.ServiceTypes.Firewall:
      return buildCommonAuthHeader(firewall.ak, firewall.sk, requestPath);
    case Enums.ServiceTypes.CloudAudit:
      return buildCommonAuthHeader(cloudAudit.ak, cloudAudit.sk, requestPath);
    case Enums.ServiceTypes.CloudAuditOss:
      return buildCommonAuthHeader(
        cloudAudit.ak,
        cloudAudit.sk,
        HttpHelper.filedMixedToSingular(request.headers['origin-service-path']),
      );
    case Enums.ServiceTypes.LogsAudit:
      return buildCommonAuthHeader(
        logsAudit.ak,
        logsAudit.sk,
        requestPath?.replace(/\/ctcloud\/ctyun\/.*?(?=\/)/, ''),
      );
    default: return {};
  }
};

const buildCommonAuthHeader = (ak: string | undefined, sk: string | undefined, requestPath?: string) => {
  if (!ak || !sk) { return {}; }
  const contentMD5 = MD5('').toString(Base64);
  const dateNow = getDateStringZoneUtc();
  return {
    accessKey: ak,
    contentMD5,
    requestDate: dateNow,
    hmac: HmacSHA1(`${contentMD5}\n${dateNow}\n${requestPath}`, sk).toString(Base64),
  };
};

const buildEShieldBssCrmAuthHeader = () => {
  const { ak, sk } = config.business.console;
  if (!ak || !sk) { return {}; }
  const contentMD5 = MD5('').toString(Base64);
  const utcStringNow = new Date().toUTCString();
  return {
    accessKey: ak,
    contentMD5,
    requestDate: utcStringNow,
    hmac: HmacSHA1(`${contentMD5}\n${utcStringNow}`, sk).toString(Base64),
  };
};

const buildOrderAuthHeader = (request: http.IncomingMessage) => {
  const { ak, sk } = config.business['proxy-app-order'];
  if (!ak || !sk) { return {}; }
  const contentMD5 = HttpHelper.filedMixedToSingular(request.headers.contentMD5) || MD5('').toString(Base64);
  const utcStringNow = getDateStringZoneUtc();
  return {
    accessKey: ak,
    contentMD5,
    requestDate: utcStringNow,
    platform: 3,
    hmac: HmacSHA1(`${contentMD5}\n${utcStringNow}`, sk).toString(Base64),
  };
};

const getRequestPath = (request: http.IncomingMessage) => {
  return request.url?.split('?')[0];
};

const getDateStringZoneUtc = () => {
  return dayjs().tz('UTC').format('ddd, DD MMM YYYY HH:mm:ss z');
};

const buildEopHeaders = (
  request: http.IncomingMessage,
  options: HeadersBuilderOptions,
) => {
  if (options?.shouldSkipEop) {
    return {};
  }
  const backendType = HttpHelper.filedMixedToSingular(request.headers.backend) as Enums.ServiceTypes;
  const isRegisteredType = !!backendType && Object.values<string>(Enums.ServiceTypes).includes(backendType);
  if (!isRegisteredType) {
    return {};
  }
  return {
    'ctyun-eop-ak': config.business[backendType]?.eop.ak,
    'ctyun-eop-sk': config.business[backendType]?.eop.sk,
  };
};

const buildAccessTraceHeaders = (request: http.IncomingMessage) => {
  const requestIdKey = 'cns-proxy-request-id';
  if (request.headers[requestIdKey]) {
    return {};
  }
  return {
    'cns-proxy-request-id': uuidV4(),
  };
};

const buildAccessControlHeaders = () => {
  return {
    'Access-Control-Allow-Origin': '*',
  };
};

export interface HeadersBuilderOptions {
  shouldSkipEop?: boolean,
}

export default (
  request: http.IncomingMessage,
  options: HeadersBuilderOptions = {},
): Record<string, string | number | undefined> => {
  return {
    ...buildServiceRequestAuthHeaders(request),
    ...buildEopHeaders(request, options),
    ...buildAccessTraceHeaders(request),
    ...buildAccessControlHeaders(),
  };
};
